/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    说明：
    使用@Aspect注解将一个java类定义为切面类
    使用@Pointcut定义一个切入点，可以是一个规则表达式，比如下例中某个package下的所有函数，也可以是一个注解等。
    根据需要在切入点不同位置的切入内容
    使用@Before在切入点开始处切入内容
    使用@After在切入点结尾处切入内容
    使用@AfterReturning在切入点return内容之后切入内容（可以用来对处理返回值做一些加工处理）
    使用@Around在切入点前后切入内容，并自己控制何时执行切入点自身的内容
    使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
 *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/
/*-------------------------------------------------------
    切点表达式：
    任意公共方法的执行：execution(public * *(..))
    任何一个以“set”开始的方法的执行：execution(* set*(..))
    AccountService 接口的任意方法的执行：execution(* com.xyz.service.AccountService.*(..))
    定义在service包里的任意类任意方法的执行：execution(* com.xyz.service.*.*(..))
    定义在service包或者子包里的任意方法的执行：execution(* com.xyz.service..*.*(..))
    在service包里的任意连接点（在Spring AOP中只是方法执行）：within(com.xyz.service.*)
    在service包或者子包里的任意连接点(在Spring AOP中只是方法执行):within(com.xyz.service..*)
    实现了AccountService接口的代理对象的任意连接点（在Spring AOP中只是方法执行）：this(com.xyz.service.AccountService)
        'this'在binding form中用的更多：- 请常见以下讨论通知的章节中关于如何使得代理对象可以在通知体内访问到的部分。
    实现了 AccountService 接口的目标对象的任意连接点（在Spring AOP中只是方法执行） ：
        target(com.xyz.service.AccountService)
        'target'在binding form中用的更多：- 请常见以下讨论通知的章节中关于如何使得目标对象可以在通知体内访问到的部分。
    任何一个只接受一个参数，且在运行时传入的参数实现了 Serializable 接口的连接点 （在Spring AOP中只是方法执行）
    args(java.io.Serializable)
    'args'在binding form中用的更多：- 请常见以下讨论通知的章节中关于如何使得方法参数可以在通知体内访问到的部分。
    请注意在例子中给出的切入点不同于 execution(* *(java.io.Serializable))： args只有在动态运行时候传入参数是可序列化的（Serializable）才匹配，
    而execution 在传入参数的签名声明的类型实现了 Serializable 接口时候匹配。
    有一个 @Transactional 注解的目标对象中的任意连接点（在Spring AOP中只是方法执行）
    @target(org.springframework.transaction.annotation.Transactional)
    '@target' 也可以在binding form中使用：请常见以下讨论通知的章节中关于如何使得annotation对象可以在通知体内访问到的部分。
    任何一个目标对象声明的类型有一个 @Transactional 注解的连接点（在Spring AOP中只是方法执行）
    @within(org.springframework.transaction.annotation.Transactional)
    '@within'也可以在binding form中使用：- 请常见以下讨论通知的章节中关于如何使得annotation对象可以在通知体内访问到的部分。
    任何一个执行的方法有一个 @Transactional annotation的连接点（在Spring AOP中只是方法执行）
    @annotation(org.springframework.transaction.annotation.Transactional)
    '@annotation' 也可以在binding form中使用：- 请常见以下讨论通知的章节中关于如何使得annotation对象可以在通知体内访问到的部分。
    任何一个接受一个参数，并且传入的参数在运行时的类型实现了 @Classified annotation的连接点（在Spring AOP中只是方法执行）
    @args(com.xyz.security.Classified)
 ------------------------------------------------------------------------------*/
package com.trade.common.aop;

import lombok.extern.log4j.Log4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * Class Description: Web层的日志切面
 *
 * @Version v1.0 2017/9/17 13:27
 * @JDK version 1.8.0_51
 * @Author zfb
 */
@Log4j
@Aspect
@Order(2)  //在切入点前的操作(before)，按order的值由小到大执行，在切入点后的操作(after,afterReturing)，按order的值由大到小执行
@Component
public class WebLogAspect {

    ThreadLocal<Long> satrtTime = new ThreadLocal<>();

    @Pointcut("execution(public * com.trade.module..controller.*.*(..))")
    public void webLog(){}

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        //记录进入切点时间
        satrtTime.set(System.currentTimeMillis());

        //接收到请求，获取请求参数，记录请求内容
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        //日志打印
        log.info("URL:"+request.getRequestURL().toString()); //请求的URL
        log.info("HTTP_METHOD:"+request.getMethod());        //请求方法
        log.info("IP:"+request.getRemoteAddr());             //请求ip
        log.info("CLASS_METHOD:"+joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName()); //类全名
        log.info("ARGS:"+ Arrays.toString(joinPoint.getArgs()));  //请求参数

    }

    @AfterReturning(returning = "ret",pointcut = "webLog()")
    public void doAfterReturing(Object ret) throws Throwable {
        //处理完成，返回内容
        log.info("RESPONSE:"+ ret);
        log.info("SPEND TIME:"+(System.currentTimeMillis() - satrtTime.get()));
    }

    public static void sayHello(){
        System.out.println("Hello World！！！");
    }
}
